//
// Copyright (c) 2010 Luís Reis
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Pine.Core
{
	public sealed class Number : Value, IEquatable<Number>
	{
		public static void CastToNumber(ValueList rightlist) {
			ICollection<Value> keys = new List<Value>(rightlist.Keys);
			foreach (Value key in keys)
			{
				rightlist.SetArrayElement(key, new Number(rightlist.GetArrayElement(key)));
			}
		}
		
		public static double? CastToNumber(Value value) {
			Number num = value as Number;
			if (num != null)
				return num.Value;
			
			StringValue str = value as StringValue;
			if (str != null) {
				double dbl;
				return double.TryParse(str.Value, out dbl) ? (double?) dbl : null;
			}
			
			CharValue chr = value as CharValue;
			if (chr != null) {
				return (double) chr.Value;
			}
			
			return null;
		}
		
		public double Value { get; private set; }
		private Function PowWrapper;
		private Function ToStringWrapper;
		private Function TimesWrapper;
		
		public Number (Value val)
		{
			double? num = CastToNumber(val);
			if (num == null)
				throw new PineException("Could not cast value to number");
			Value = (double) num;
		}
		
		public Number (double val)
		{
			Value = val;
			PowWrapper = new Function(null, Pow);
			ToStringWrapper = new Function(null, ToStringAdapted);
			TimesWrapper = new Function(null, Times);
		}
		
		public void Times(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl", "Argument list of function can not be null");
			
			int times = (int) Math.Round(Value);
			Value val = rl.GetArrayElement(new Number(0));
			rl.Clear();
			if (val is Function) {
				for (int i = 0; i < times; ++i) {
					rl.Clear();
					rl.SetArrayElement(new Number(0), new Number(i));
					val.Invoke(rl);
				}
			}
		}
		
		public void Pow(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl", "Argument list of function can not be null");
			
			Value val = rl.GetArrayElement(new Number(0));
			rl.Clear();
			
			Number nval = val as Number;
			if (nval == null)
				throw new PineException("Argument 0 of pow function must be number");
			
			rl.SetArrayElement(new Number(0), new Number(Math.Pow(Value, nval.Value)));
		}
		
		public void ToStringAdapted(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl", "Argument list of function can not be null");
			
			rl.Clear();
			rl.SetArrayElement(new Number(0), new StringValue(ToString()));
		}
		
		public override string ToString ()
		{
			return Value.ToString(CultureInfo.InvariantCulture);
		}
		
		public override Value Add (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new Number(Value + nright.Value);
			StringValue sright = right as StringValue;
			if (sright != null)
				return new StringValue(Value + sright.Value);
			return base.Add (right);
		}
		
		public override Value Subtract (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new Number(Value - nright.Value);
			return base.Subtract (right);
		}
		
		public override Value Multiply (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new Number(Value * nright.Value);
			return base.Multiply (right);
		}
		
		public override Value Greater (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new BooleanValue(Value > nright.Value);
			return base.Greater (right);
		}
		
		public override Value GreaterOrEqual (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new BooleanValue(Value >= nright.Value);
			return base.GreaterOrEqual (right);
		}
		
		public override Value Less (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new BooleanValue(Value < nright.Value);
			return base.Greater (right);
		}
		
		public override Value LessOrEqual (Value right)
		{
			Number nright = right as Number;
			if (nright != null)
				return new BooleanValue(Value <= nright.Value);
			return base.GreaterOrEqual (right);
		}
		
		public override Value Increment ()
		{
			return new Number(Value + 1);
		}
		
		public override Value Decrement ()
		{
			return new Number(Value - 1);
		}
		
		public override Value GetPineType() {
			return PineType.NumberType;
		}
		
		public override bool Equals (object obj)
		{
			return Equals(obj as Number);
		}
		
		public bool Equals(Number other)
		{
			 return other != null && Value == other.Value;
		}
		
		public override int GetHashCode ()
		{
			return Value.GetHashCode();
		}
		
		public override Value FieldGet (string fieldname)
		{
			if (fieldname == "pow")
				return PowWrapper;
			if (fieldname == "toString")
				return ToStringWrapper;
			if (fieldname == "times")
				return TimesWrapper;
			throw new PineException("No such field in number: " + fieldname);
		}
	}
}

